/* jslight_js.js -- Javascript highlighter for jslight */
/* Copyright 2007 Rici Lake. */
/* You may use this software under the terms of the MIT license
 * as shown at http://www.opensource.org/licenses/mit-license.php
 */

if (!this.jslight) {this.jslight = {"language":{}}; }
(function() {
  jslight.language.js = {}

  var words = {}
  jslight.addAnnotations(words, [
    [ "statement", [ "break", "continue", "return", "throw", "with"]],
    [ "label",     [ "case", "default"]],
    [ "conditional",["if", "catch", "else", "finally", "switch", "try"]],
    [ "repeat",    [ "do", "for", "while"]],
    [ "operator",  [ "delete", "in", "instanceof", "new", "typeof", "void"]],
    [ "type",      [ "var", "function",
                     "Array", "Boolean", "Date", "Function",
                     "Number", "Object", "String", "RegExp"]],
    [ "identifier",[ "this", "arguments"]],
    [ "constant",  [ "false", "null", "true",
                     "undefined", "NaN", "Infinity"]],
// Reserved words
    [ "special",   [ "abstract", "enum", "int", "short",
                     "boolean", "export", "interface", "static",
                     "byte", "extends", "long", "super",
                     "char", "final", "native", "synchronized",
                     "class", "float", "package", "throws",
                     "const", "goto", "private", "transient",
                     "debugger", "implements", "protected", "volatile",
                     "double", "import", "public"]]
  ]);

  /* 1: whitespace    2: comments      3: strings      4: regexen or / /=
   * 5: numbers       6: number check  7: identifiers  8: punctuation
   * 9: errors
   */
  var baselexnoregex =
/(\s+)|(\/\/.*|\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\/)|("(?:[^\\"]|\\.)*"|'(?:[^\\']|\\.)*')|(\/=?)|(0[xX][0-9a-fA-F]+|(?:(?:0(?!\d)|[1-9]\d*)(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)([a-zA-Z]*)|([a-zA-Z$_][\w$_]*)|([=!]=?=?|&amp;(?:&amp;|=)?|[|][|=]?|(?:&lt;){1,2}=?|(?:&gt;){1,3}=?|[+][+=]?|-[=-]?|[*%^]=?|[{}()[\]\.;,?:~])|(.)/g;
  var baselexregex =
/(\s+)|(\/\/.*|\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\/)|("(?:[^\\"]|\\.)*"|'(?:[^\\']|\\.)*')|(\/(?:[^\\\/]|\\.)*\/\w*)|(0[xX][0-9a-fA-F]+|(?:(?:0(?!\d)|[1-9]\d*)(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)([a-zA-Z]*)|([a-zA-Z$_][\w$_]*)|([=!]=?=?|&amp;(?:&amp;|=)?|[|][|=]?|(?:&lt;){1,2}=?|(?:&gt;){1,3}=?|[+][+=]?|-[=-]?|[*%^]=?|[{}()[\]\.;,?:~])|(.)/g;
  var postfixPunctuation = { ")" : true, "]" : true, "}" : true };
  var neutralPunctuation = { "++" : true, "--" : true };
  var idClass = { "constant": true, "type": true, "identifier": true, "none": true };

  /* The first set are valid. The . will match anything (other than an eol, which can't appear)
   * which must be an error. Actually, letters are legal but they don't carry special meaning, so
   * they might as well be flagged as an error.
   */
  var stringregex = /\\(?:([bfnrtv]|&\w+;|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|0(?!\d)|[^xu0-9\n])|(.))/g
  function highlightEscape(m, a1, a2) {
    return jslight.annotate(m, a1 ? "special" : "error");
  } 
  function dostring(m) {
    return m.replace(stringregex, highlightEscape);
  }

  /* 1: escape   2:  escape error   3:  char   4: dash  5: escape 6: escape error 7: char */
  var cclassregex = /(?:(\\(?:[bdDsSWwWfnrtv]|c[a-zA-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-f]{4}|0(?!\d)|[1-9]\d*|\W))|(\\.)|(.))(?:(-)(?:(\\(?:[bdDsWwWfnrtv]|c[a-zA-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-f]{4}|0(?!\d)|[1-9]\d*|\W))|(\\.)|(.)))?/g;

  function highlightRange(a1, a2, a3) {
    if (a1) return jslight.annotate(a1, "regex_escape");
    else if (a2) return jslight.annotate(a2, "error");
    else return jslight.annotate(a3);
  }

  function highlightClass(m, a1, a2, a3, a4, a5, a6, a7) {
    var r = highlightRange(a1, a2, a3);
    if (a4) r += jslight.annotate(a4, "regex_op")+highlightRange(a5, a6, a7)
    return r;
  }

  function doclass(s) {
    return s.replace(cclassregex, highlightClass);
  }

  /*  1: open paren    2: close paren     3: alternative   4: quantifier
   *  5: assertion or escape              6,7,8: char class (unlexed; 7 is the guts)
   *  9: error         10: / (and flags)  11: any other character
   */
  var regexregex =
/(\((?!\?)|\(\?[:=!])|(\))|(\|)|((?:[*+?]|\{\d+(?:,\d*)?\})\??)|([$^.]|\\(?:[bBdDsSwWfnrtv]|c[a-zA-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|0(?!\d)|[1-9]\d*|\W))|(\[\^?)((?:[^\\\]]|\\.)*)(\])|([\[\{\}]|\\.|\(.)|(^\/)|(\/\w*$)|(.)/g;
  function regexFactory(big) {
    var pdepth = 0;
    var started = false;
    var broken = false;
    function highlight(m, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) {
      if (a1) { pdepth++; return jslight.annotate(m, "regex_paren"); }
      else if (a2) {
        if (pdepth > 0) { --pdepth; return jslight.annotate(m, "regex_paren"); }
        else return jslight.annotate(m, "error");
      }
      else if (a3) {
        if (big && pdepth == 0) {
          broken = true;
          return "<br />" + jslight.annotate(m, "regex_op");
        }
        else
          return jslight.annotate(m, "regex_op");
      }
      else if (a4) return jslight.annotate(m, "regex_op");
      else if (a5) return jslight.annotate(m, "regex_escape");
      else if (a6) return jslight.annotate(a6, "regex_paren")+doclass(a7)+jslight.annotate(a8, "regex_paren");
      else if (a9) return jslight.annotate(m, "error");
      else if (a10) {
        if (started) {
          var r = (broken ? "<br />" : "");
          var tag = (pdepth == 0) ? "regex_op" : "error";
          if (m.length == 1)
            return r+jslight.annotate(m, tag);
          else
            return r+jslight.annotate(m.substr(0,0), tag)+jslight.annotate(m.substr(1));
        }
        else {
          started = true;
          return jslight.annotate(m, "regex_op");
        }
      }
      else return jslight.annotate(m);
    }
    return function(s) { return s.replace(regexregex, highlight); }
  }

  function doregex(s) {
    if (s.length > 40) {
      return "<div class='regex'>"+s.replace(/ /, "&nbsp;").replace(regexregex, regexFactory(true))+"</div>";
    } 
    else return s.replace(regexregex, regexFactory(false));
  }

  jslight.language.js.getHighlighter = function() {
    var lexnoregex = new RegExp(baselexnoregex), lexregex = new RegExp(baselexregex);
    var lex = lexregex;
    var lastIndex = 0;

    function dopunctuation(m) {
      if (m in postfixPunctuation) lex = lexnoregex;
      else if (!(m in neutralPunctuation)) lex = lexregex;
      return jslight.annotate(m);
    }

    function highlightLexeme(a) {
      var m = a[0];
      if (a[1]) return jslight.annotate(m);
      else if (a[2]) return jslight.annotate(m, "comment");
      else if (a[3]) { lex = lexnoregex; return jslight.annotate(dostring(m), "constant"); }
      else if (a[4]) {
        if (m.length <= 2) return dopunctuation(m);
        else { lex = lexnoregex; return jslight.annotate(doregex(m), "constant"); }
      }
      else if (a[5]) {
        lex = lexnoregex;
        if (a[6]) return jslight.annotate(a[5], "constant")+jslight.annotate(a[6], "special")
        else return jslight.annotate(m, "constant");
      }
      else if (a[7]) { 
        var t = words[m];
        lex = (t in idClass) ? lexregex : lexnoregex;
        return jslight.annotate(m, t);
      }
      else if (a[8]) return dopunctuation(m); 
      else return jslight.annotate(m, "error");
    }

    function nextToken(s) {
      lex.lastIndex = lastIndex;
      var matches = lex.exec(s);
      if (!matches) return null;
      lastIndex = lex.lastIndex;
      return highlightLexeme(matches);
    }

    return function(s) {
      var rv = "";
      var seg;
      while ((seg = nextToken(s)) !== null) rv += seg;
      if (lastIndex < s.length) rv += jslight.annotate(s.substring(lastIndex), "error");
      return rv;
    } 
  }
})()
